/********************************************
 * 功能说明: 
 * 模块名称: 
 * 系统名称: 
 * 软件版权: Frank
 * 系统版本: 1.0.0
 * 开发人员: Frank
 * 开发时间: 2019/12/23 15:03
 * 审核人员: 
 * 相关文档: 
 * 修改记录: 修改日期 修改人员 修改说明
 *********************************************/
package com.spring.arch.uaa.config;

import com.google.common.cache.Cache;
import com.spring.arch.common.constants.IConstants;
import com.spring.arch.common.security.CurrentUserContextHolder;
import com.spring.arch.common.security.SecurityLog;
import com.spring.arch.common.security.SecurityProperties;
import com.spring.arch.common.security.UserPrincipal;
import com.spring.arch.common.utils.HttpUtils;
import com.spring.arch.uaa.oauth2.service.OauthUserDetailsServiceImpl;
import com.spring.arch.common.constants.IConstants;
import com.spring.arch.common.security.CurrentUserContextHolder;
import com.spring.arch.common.security.SecurityLog;
import com.spring.arch.common.security.SecurityProperties;
import com.spring.arch.common.security.UserPrincipal;
import com.spring.arch.common.utils.HttpUtils;
import com.spring.arch.uaa.oauth2.service.OauthUserDetailsServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.UUID;

/**
 * 应用过滤器
 * @author Frank
 * @version 1.0.0.1
 * @since 2019/12/23 15:03
 */
@Order(value = Integer.MAX_VALUE-1)
@Slf4j
@Component
public class AuthenticationFilter extends OncePerRequestFilter {

    private static final String SECURITY_IGNORE_URLS_SPILT_CHAR = ",";
    @Autowired
    private Cache<String, UserPrincipal> jwtLocalCache;
    @Autowired
    private SecurityProperties securityProperties;

    @Autowired
    private OauthUserDetailsServiceImpl oauthUserDetailsService;

    @Override
    protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
        final String excludeAuthUrl = securityProperties.getExcludeAuthUrl();
        final String clientIp = HttpUtils.getClientIP(request);
        final String method = request.getMethod();
        final String requestURI = request.getRequestURI();
        if (StringUtils.isBlank(excludeAuthUrl)) {
            return false;
        }
        boolean matchers = HttpUtils.antMatchers(request, StringUtils.split(excludeAuthUrl, SECURITY_IGNORE_URLS_SPILT_CHAR));
        if (matchers) {
            return true;
        }

        final String jwt = HttpUtils.parseToken(request);
        if (StringUtils.isBlank(jwt)) {
            SecurityLog.authentication.warn("[{}]JWT empty token: {} {}", clientIp, method, requestURI);
            return false;
        }

        final String clientId = request.getHeader(IConstants.HEADER_CLIENT_ID);
        if (StringUtils.isBlank(clientId)) {
            SecurityLog.signUp.warn("[{}] clientId empty！！！: {} {}", clientIp, method, requestURI);
            return true;
        }
        return false;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {
        final String requestURI = request.getRequestURI();
        final String clientIp = HttpUtils.getClientIP(request);
        final String method = request.getMethod();
        final String clientId = request.getHeader(IConstants.HEADER_CLIENT_ID);
        final String jwt = HttpUtils.parseToken(request);

        SecurityLog.authentication.info("ApplicationFilter [{}-{}] : {} {} {}", clientIp, clientId, method, requestURI, jwt);

        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication == null || !authentication.isAuthenticated()) {
            // token未认证
            SecurityLog.authentication.warn("[{}] token has not been authenticated: {} {}", clientIp, method, requestURI);
            response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "UNAUTHORIZED: token has not been authenticated");
            return;
        }

        if (StringUtils.isNotBlank(clientId)) {
            try {
                // 缓存更新
                UserPrincipal userPrincipal = jwtLocalCache.getIfPresent(jwt);
                boolean matchers = HttpUtils.antMatchers(request, "/**/auth/me");
                if (matchers || userPrincipal == null) {
                    CurrentUserContextHolder.clear();
                    userPrincipal = oauthUserDetailsService.loadUserPrincipal(authentication.getName(), UUID.fromString(clientId));
                    if (userPrincipal == null) {
                        SecurityLog.authentication.warn("[{}] request uaa for principal failure: {} {}", clientIp, method, requestURI);
                        response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "request uaa for principal failure");
                        return;
                    }
                    // 设置缓存
                    jwtLocalCache.put(jwt, userPrincipal);
                }
                CurrentUserContextHolder.setCurrentUser(userPrincipal);
            } catch (final Exception ex) {
                SecurityLog.authentication.error("[{}] oauth2 authentication failed: {} {}, {}, {}, {}",
                        clientIp, method, requestURI, jwt, ex.getMessage(), ex);
                response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "UNAUTHORIZED: " + ex.getMessage());
                return;
            }
        }

        filterChain.doFilter(request, response);
    }
}
